using Gj;
using Unity.Burst;
using Unity.Burst.Intrinsics;
using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Transforms;
using UnityEngine;
using BoxCollider = Unity.Physics.BoxCollider;
using Collider = Unity.Physics.Collider;
using RaycastHit = Unity.Physics.RaycastHit;
using SphereCollider = Unity.Physics.SphereCollider;

[WorldSystemFilter(WorldSystemFilterFlags.Disabled)]
[UpdateAfter(typeof(CastTrailSystem))]
public unsafe partial class CastColliderSystem : SystemBase
{
    private EntityQuery be_ray_Group;
    private EntityQuery collider_Group;

    private EndSimulationEntityCommandBufferSystem CommandBufferSystem;
    private EntityQuery ray_Group;
    private EntityQuery ray_hit_Group;
    private EntityQuery view_Group;

    private PhysicsWorld world;

    protected override void OnCreate()
    {
        CommandBufferSystem = World.GetOrCreateSystemManaged<EndSimulationEntityCommandBufferSystem>();

        be_ray_Group = new EntityQueryBuilder(Allocator.Temp)
            .WithAllRW<BeRayHitsData>().Build(this);

        collider_Group = new EntityQueryBuilder(Allocator.Temp)
            .WithAllRW<CastColliderData>().Build(this);

        ray_Group = new EntityQueryBuilder(Allocator.Temp)
            .WithAllRW<CastRayData>().Build(this);

        view_Group = new EntityQueryBuilder(Allocator.Temp)
            .WithAllRW<CastViewData>().Build(this);
    }

    // OnUpdate runs on the main thread.
    protected override void OnUpdate()
    {
        var inputDeps = Dependency;
        var time = SystemAPI.Time.ElapsedTime;

        var translationData = GetComponentLookup<LocalTransform>();

        var commandBuffer = CommandBufferSystem.CreateCommandBuffer().AsParallelWriter();

        var physicsWorld = SystemAPI.GetSingletonRW<PhysicsWorldSingleton>().ValueRW.PhysicsWorld;
        var EntityType = GetEntityTypeHandle();
        inputDeps = new ClearRayHitJob
        {
            CommandBuffer = commandBuffer,
            EntityType = EntityType
        }.Schedule(be_ray_Group, inputDeps);

        inputDeps = new CastColliderJob
        {
            World = physicsWorld,
            CommandBuffer = commandBuffer,
            Time = time
        }.Schedule(collider_Group, inputDeps);

        inputDeps = new CastRayJob
        {
            World = physicsWorld,
            CommandBuffer = commandBuffer,
            Time = time
        }.Schedule(ray_Group, inputDeps);

        inputDeps = new CastViewJob
        {
            World = physicsWorld,
            CommandBuffer = commandBuffer,
            Time = time,
            translationData = translationData
        }.Schedule(view_Group, inputDeps);
        CommandBufferSystem.AddJobHandleForProducer(inputDeps);
        inputDeps.Complete();
    }

    [BurstCompile]
    private partial struct CastViewJob : IJobEntity
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        [ReadOnly] public PhysicsWorld World;
        [ReadOnly] public ComponentLookup<LocalTransform> translationData;

        [ReadOnly] public double Time;

        public void Execute([EntityIndexInChunk] int index, Entity entity, in CastViewData castViewData)
        {
            var colliderCastHits = new NativeList<ColliderCastHit>(Allocator.Temp);
            var collider = SphereCollider.Create(new SphereGeometry
            {
                Center = float3.zero,
                Radius = castViewData.radius
            });
            var colliderCastInput = new ColliderCastInput
            {
                Collider = (Collider*)collider.GetUnsafePtr(),
                Orientation = quaternion.identity,
                Start = castViewData.origin,
                End = castViewData.origin
            };
            World.CastCollider(colliderCastInput, ref colliderCastHits);
            var entitys = new NativeList<Entity>(Allocator.Temp);
            for (var i = 0; i < colliderCastHits.Length; i++)
            {
                if (World.Bodies.Length <= colliderCastHits[i].RigidBodyIndex ||
                    colliderCastHits[i].RigidBodyIndex < 0) continue;
                var e = World.Bodies[colliderCastHits[i].RigidBodyIndex].Entity;
                var a = false;
                if (!translationData.HasComponent(e)) continue;
                if (castViewData.angle > 0)
                {
                    float3 vDir = Vector3.Normalize(translationData[e].Position - castViewData.origin); //与目标连线的向量
                    //计算两个向量间的夹角
                    var angle = Vector3.Angle(castViewData.direction, vDir);
                    if (angle <= castViewData.angle / 2) a = true;
                }
                else
                {
                    a = true;
                }

                if (a) entitys.Add(e);
            }

            var buffer = CommandBuffer.AddBuffer<ViewHitsData>(index, entity);

            if (entitys.Length > 0)
                for (var i = 0; i < entitys.Length; i++)
                    buffer.Add(new ViewHitsData { entity = entitys[i] });
            CommandBuffer.AddComponent(index, entity, new HitData { time = Time, length = buffer.Length });
            entitys.Dispose();
            collider.Dispose();
            colliderCastHits.Dispose();
            CommandBuffer.RemoveComponent<CastViewData>(index, entity);
        }
    }

    [BurstCompile]
    private partial struct CastColliderJob : IJobEntity
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        [ReadOnly] public PhysicsWorld World;

        [ReadOnly] public double Time;

        public void Execute([EntityIndexInChunk] int index, Entity entity, in CastColliderData castColliderData)
        {
            var colliderCastHits = new NativeList<ColliderCastHit>(Allocator.Temp);
            BlobAssetReference<Collider> collider;
            switch (castColliderData.type)
            {
                case ColliderType.Box:
                    collider = BoxCollider.Create(new BoxGeometry
                    {
                        Center = float3.zero,
                        Size = castColliderData.radius,
                        Orientation = quaternion.identity
                    });
                    break;
                default:
                    collider = SphereCollider.Create(new SphereGeometry
                    {
                        Center = float3.zero,
                        Radius = castColliderData.radius
                    });
                    break;
            }

            var colliderCastInput = new ColliderCastInput
            {
                Collider = (Collider*)collider.GetUnsafePtr(),
                Orientation = quaternion.identity,
                Start = castColliderData.Start,
                End = castColliderData.End
            };
            World.CastCollider(colliderCastInput, ref colliderCastHits);
            var buffer = CommandBuffer.AddBuffer<CastHitsData>(index, entity);
            if (colliderCastHits.Length > 0)
                for (var i = 0; i < colliderCastHits.Length; i++)
                {
                    var colliderCastHit = colliderCastHits[i];
                    if (World.Bodies.Length <= colliderCastHit.RigidBodyIndex ||
                        colliderCastHit.RigidBodyIndex < 0) continue;

                    buffer.Add(new CastHitsData { entity = World.Bodies[colliderCastHit.RigidBodyIndex].Entity });
                }

            CommandBuffer.AddComponent(index, entity, new HitData { time = Time, length = buffer.Length });
            collider.Dispose();
            colliderCastHits.Dispose();
            CommandBuffer.RemoveComponent<CastColliderData>(index, entity);
        }
    }

    [BurstCompile]
    private partial struct CastRayJob : IJobEntity
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        [ReadOnly] public PhysicsWorld World;

        [ReadOnly] public double Time;

        public void Execute([EntityIndexInChunk] int index, Entity entity, in CastRayData castRayData)
        {
            if (!castRayData.multiple)
            {
                if (World.CastRay(new RaycastInput
                    {
                        Start = castRayData.Start,
                        End = castRayData.End,
                        Filter = CollisionFilter.Default
                    }, out var hit))
                    if (World.Bodies.Length > hit.RigidBodyIndex && hit.RigidBodyIndex >= 0)
                    {
                        var beEntity = World.Bodies[hit.RigidBodyIndex].Entity;
                        var normal = double.IsNaN(hit.SurfaceNormal.x) ? new float3(0, 0, 0) : hit.SurfaceNormal;
                        CommandBuffer.AddComponent(index, entity,
                            new RayHitData { point = hit.Position, normal = normal, entity = beEntity });
                        if (!castRayData.isScan)
                            CommandBuffer.AddBuffer<BeRayHitsData>(index, beEntity).Add(new BeRayHitsData
                                { point = hit.Position, normal = hit.SurfaceNormal, entity = entity });
                    }
            }
            else
            {
                var allHits = new NativeList<RaycastHit>(Allocator.Temp);
                World.CastRay(new RaycastInput
                {
                    Start = castRayData.Start,
                    End = castRayData.End,
                    Filter = CollisionFilter.Default
                }, ref allHits);

                var buffer = CommandBuffer.AddBuffer<RayHitsData>(index, entity);
                if (allHits.Length > 0)
                    for (var i = 0; i < allHits.Length; i++)
                    {
                        var castHit = allHits[i];
                        if (World.Bodies.Length <= castHit.RigidBodyIndex || castHit.RigidBodyIndex < 0) continue;
                        var beEntity = World.Bodies[castHit.RigidBodyIndex].Entity;
                        buffer.Add(new RayHitsData
                            { entity = beEntity, normal = castHit.SurfaceNormal, point = castHit.Position });
                        if (!castRayData.isScan)
                            CommandBuffer.AddBuffer<BeRayHitsData>(index, beEntity).Add(new BeRayHitsData
                                { point = castHit.Position, normal = castHit.SurfaceNormal, entity = entity });
                    }

                allHits.Dispose();
            }

            CommandBuffer.RemoveComponent<CastRayData>(index, entity);
        }
    }

    [BurstCompile]
    private struct ClearRayHitJob : IJobChunk
    {
        [WriteOnly] public EntityCommandBuffer.ParallelWriter CommandBuffer;

        [ReadOnly] public EntityTypeHandle EntityType;

        public void Execute(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask,
            in v128 chunkEnabledMask)
        {
            var entities = chunk.GetNativeArray(EntityType);

            for (var i = 0; i < chunk.Count; i++)
            {
                var entity = entities[i];
                CommandBuffer.RemoveComponent<BeRayHitsData>(unfilteredChunkIndex, entity);
            }
        }
    }
}